/*
该模块负责将日志落地到指定的位置(标准输出、文件、滚动文件)
    1. 抽象落地基类
    2. 派生子类
    3. 使用工厂模式进行创建与表示的分离
*/

#ifndef __M_SINK_H__
#define __M_SINK_H__

#include "Util.hpp"
#include <memory>
#include <fstream> // ofstream
#include <cassert>
#include <sstream>

namespace LogModule
{
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink() {}
        // 对于不同的场景，需要的析构函数不同
        virtual ~LogSink() {}
        // 每个派生类必须要实现的有 -- 将长度为len的data数据写入到指定的位置
        virtual void Log(const char *data, size_t len) = 0;
    };
    // 1. 写入到标准输出
    // 直接将数据写入到标准输出即可
    class StdOutLogSink : public LogSink
    {
    public:
        ~StdOutLogSink() {}
        void Log(const char *data, size_t len) override
        {
            std::cout.write(data, len);
        }
    };
    // 2. 写入到特定文件
    // 将数据写入到特定文件中，对于不存在的文件，要先创建文件
    class FileLogSink : public LogSink
    {
    public:
        // 写入到特定文件，首先要有这个文件才行
        FileLogSink(const std::string &filename) : _filename(filename)
        {
            // 1. 创建文件所在目录
            UtilModule::File::CreateDirectory(UtilModule::File::Path(_filename));
            // 2. 打开文件，如果没有该文件，创建该文件
            _ofs.open(_filename.c_str());
            assert(_ofs.is_open());
        }
        void Log(const char *data, size_t len) override
        {
            // 有了文件，直接将数据写入文件中即可
            _ofs.write(data, len);
            assert(_ofs.good()); // 判断文件流被操作后是否完整
        }

    private:
        std::string _filename;
        std::ofstream _ofs; // 文件输出流
    };
    // 3. 写入滚动文件中 -- 我们实现的是大小滚动的滚动文件
    // 文件一直在改变，先获取文件所在的目录路径，文件一旦达到指定的大小
    // 就在该目录路径下重新创建新的日志文件
    class RollBySizeLogSink : public LogSink
    {
    public:
        RollBySizeLogSink(const std::string &basename, size_t max_fsize)
            : _basename(basename), _max_fsize(max_fsize), _name_count(0), _cur_fsize(0)
        {
            // 1. 先获取新的文件名称 -- ./log/log-年月日时分秒-01
            std::string _filename = CreateNewFile();
            UtilModule::File::CreateDirectory(UtilModule::File::Path(_filename));
            _ofs.open(_filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }
        void Log(const char *data, size_t len) override
        {
            if (_cur_fsize + len >= _max_fsize)
            {
                // 注意：一定要关闭之前打开的文件
                _ofs.close();
                // 如果文件内存不够，那么就需要重新创建文件
                std::string pathname = CreateNewFile();
                _ofs.open(pathname, std::ios::binary | std::ios::app);
                _cur_fsize = 0;
                assert(_ofs.is_open());
            }
            _ofs.write(data, len);
            _cur_fsize += len;
            
            assert(_ofs.good());
        }

    private:
        // 一旦文件内存不够，需要重新创建日志文件
        std::string CreateNewFile()
        {
            std::stringstream filename;
            time_t t = UtilModule::Date::Now();
            struct tm ltime;
            localtime_r(&t, &ltime);
            filename << _basename;
            filename << ltime.tm_year + 1900; // 从1900年开始为第0年，所以要加上1900
            filename << ltime.tm_mon + 1;     // 从0开始计算，所以要加1才是现实的月份
            filename << ltime.tm_mday;
            filename << ltime.tm_hour;
            filename << ltime.tm_min;
            filename << ltime.tm_sec;
            filename << "-";
            filename << _name_count++;
            filename << ".log";
            return filename.str();
        }

    private:
        std::string _basename;
        std::ofstream _ofs;
        size_t _max_fsize; // 文件最大存储容量
        size_t _cur_fsize; // 文件当前的存储容量
        size_t _name_count;
    };

    // 工厂模式 -- 通过工厂就可以直接创建出对象，不需要自己创建
    class SinkFactory
    {
    public:
        template <typename SinkType, typename ...Args>
        static LogSink::ptr Create(Args&& ...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}

#endif